﻿using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using System;

namespace CryptFighter
{
    /// <summary>
    /// Define un nuevo objeto del tipo Personaje, controlable por el usuario.
    /// </summary>
    class Personaje
    {
        // Se definen las constantes comunes para este personaje
        private float PROPORCION_ANCHO;
        private float PROPORCION_ALTO;
        private int START_POSITION_X;
        private int START_POSITION_Y;
        private int START_MARGIN;
        const int PROFUNDIDAD = 1;
        const int CHAR1_SPEED = 100;
        const int MOVE_UP = -1;
        const int MOVE_DOWN = 1;
        const int MOVE_LEFT = -1;
        const int MOVE_RIGHT = 1;

        public int Victorias = 0;
        public int Empates = 0;
        public Color ColorJugador;

        private bool Firstload;
        private bool Colisionado;
        // Controlar si permitimos movimiento.
        private bool MoverIzquierdaJ1 = true;
        private bool MoverDerechaJ1 = true;
        private bool MoverIzquierdaJ2 = true;
        private bool MoverDerechaJ2 = true;
        // Controlar si existe movimiento.
        public bool Izquierda = false;
        public bool Derecha = false;
        public bool Atacando = false;
        public bool Bloqueando = false;
        public bool Saltando = false;

        private bool fast = false;
        private bool mid = false;
        private bool strong = false;

        private Animacion idleAnimation;
        private Animacion moveAnimation;
        private Animacion fastpunchAnimation;
        private Animacion midpunchAnimation;
        private Animacion strongpunchAnimation;
        private Animacion blockAnimation;
        private Animacion jumpAnimation;
        private SpriteEffects flip = SpriteEffects.None;
        private AnimacionPersonaje Secuencia;

        int MaxLim;
        int MinLim;
        int MaxSep;
        int MinSep;

        public Vector2 Posicion;
        public Rectangle Size;

        public Puñetazo Ataque;
        /// <summary>
        /// Estructura que agrupa los retardos y daño de los diferentes tipos de puñetazo.
        /// </summary>
        public struct Puñetazo
        {
            public double tiempoPuñetazo;

            public float golpeBajo;
            public float golpeMedio;
            public float golpeAlto;
            public float golpe;
            public bool golpeAcertado;
        }

        public BarraVida vidaPersonaje;

        /// <summary>
        /// Devuelve el ancho de un fotograma de una secuencia.
        /// </summary>
        /// <returns>Devuelve el parámetro FrameWidth de la secuencia.</returns>
        public int GetFrameWidth()
        {
            return idleAnimation.FrameWidth;
        }

        /// <summary>
        /// Establece los límites del juego para los personajes.
        /// </summary>
        /// <param name="Max">Límite máximo de pantalla.</param>
        /// <param name="Min">Límite mínimo de pantalla.</param>
        public void SetLimites(int Max, int Min)
        {
            MaxLim = Max;
            MinLim = Min;
        }

        /// <summary>
        /// Establece los límites de separación entre personajes.
        /// </summary>
        /// <param name="Max">Máximo de separación.</param>
        /// <param name="Min">Mínimo de separación.</param>
        public void SetSeparacion(int Max, int Min)
        {
            MaxSep = Max;
            MinSep = Min;
        }

        private int mIndicePersonaje;
        /// <summary>
        /// Establece el indice del personaje.
        /// </summary>
        public int IndicePersonaje
        {
            get
            {
                return mIndicePersonaje;
            }
            set
            {
                mIndicePersonaje = value;
            }
        }

        /// <summary>
        /// Establece la proporción de los margenes de colisión de un personaje.
        /// </summary>
        /// <param name="Ancho">Proporción del ancho a calcular.</param>
        /// <param name="Alto">Proporción del alto a calcular.</param>
        /// <returns>Vector con las proporciones establecidas.</returns>
        public Vector2 Proporcion(float Ancho, float Alto)
        {
            PROPORCION_ANCHO = Ancho;
            PROPORCION_ALTO = Alto;

            return new Vector2(PROPORCION_ANCHO, PROPORCION_ALTO);
        }

        /// <summary>
        /// Inicia la posicion del personaje dependiendo del indice especificado.
        /// </summary>
        /// <param name="numPersonaje">Indice del personaje (1 ó 2).</param>
        /// <returns>Devuelve el vector de posicion del personaje.</returns>
        public Vector2 PosicionInicial(int numPersonaje)
        {
            if (numPersonaje == 1)
            {
                IndicePersonaje = numPersonaje;
                START_POSITION_X = 1280 + START_MARGIN;
                START_POSITION_Y = 670;
            }
            else
            {
                IndicePersonaje = 2;
                START_POSITION_X = 2560 - START_MARGIN;
                START_POSITION_Y = 670;
            }
            Posicion.X = START_POSITION_X;
            Posicion.Y = START_POSITION_Y;
            return Posicion;
        }

        public int MargenInicial
        {
            get
            {
                return START_MARGIN;
            }
            set
            {
                START_MARGIN = value;
            }
        }

        private float mEscala = 1.0f;
        /// <summary>
        /// Establece la escala del personaje.
        /// </summary>
        public float Escala
        {
            get
            {
                return mEscala;
            }
            set
            {
                mEscala = value;
                Size = new Rectangle(0, 0, (int)(contenedorPersonaje.Width * Escala), (int)(contenedorPersonaje.Height * Escala));
            }
        }

        private bool mEsHumano = true;
        /// <summary>
        /// Establece si el personaje es controlado por un jugador.
        /// </summary>
        public bool EsHumano
        {
            get
            {
                return mEsHumano;
            }
            set
            {
                mEsHumano = value;
            }
        }

        /// <summary>
        /// Inicializa la animación del personaje y los ataques.
        /// </summary>
        public void Reset()
        {
            Firstload = true;

            Secuencia.PlayAnimation(idleAnimation);

            Ataque.tiempoPuñetazo = 0;

            Ataque.golpeBajo = 2;
            Ataque.golpeMedio = 4;
            Ataque.golpeAlto = 8;

            Ataque.golpeAcertado = false;

            vidaPersonaje.Reset();
        }

        private Rectangle contenedorPersonaje;
        /// <summary>
        /// Establece un rectangulo alrededor del personaje para situarlo en el mundo.
        /// </summary>
        public Rectangle Contenedor
        {
            get
            {
                int left = (int)Math.Round(Posicion.X - Secuencia.Origin.X) + contenedorPersonaje.X;
                int top = (int)Math.Round(Posicion.Y - Secuencia.Origin.Y) + contenedorPersonaje.Y;

                return new Rectangle(left, top, contenedorPersonaje.Width, contenedorPersonaje.Height);
            }
        }

        /// <summary>
        /// Crea un rectangulo proporcional a la parte especificada para el ancho de fotograma.
        /// </summary>
        public void CrearContenedor()
        {
            int width = (int)(idleAnimation.FrameWidth * PROPORCION_ANCHO);
            int left = (idleAnimation.FrameWidth - width) / 2;
            int height = (int)(idleAnimation.FrameWidth * PROPORCION_ALTO);
            int top = idleAnimation.FrameHeight - height;

            contenedorPersonaje = new Rectangle(left, top, width, height);
        }



        // Enumeración de los diferentes estados del personaje:
        // Walking : Caminar - movimiento horizontal
        // Jumping : Saltar
        public enum State
        {
            Idle,
            Walking,
            Attacking,
            Blocking,
            Jumping
        }
        // Define el estado actual del personaje
        public State mCurrentState = State.Idle;

        // mDirection : Direccion inicial del Sprite
        // mSpeed : Velocidad inicial del Sprite
        // mStartingPosition : Posicion de salto inicial del Sprite
        Vector2 mDirection, mSpeed, mStartingPosition = Vector2.Zero;

        KeyboardState mPreviousKeyboardState;

        /// <summary>
        /// Carga el contenido del personaje en memoria.
        /// </summary>
        /// <param name="mContentManager">Gestor de contenido.</param>
        public void LoadContent(ContentManager mContentManager)
        {
            Firstload = true;

            vidaPersonaje = new BarraVida();

            idleAnimation = new Animacion(mContentManager.Load<Texture2D>("Personajes/pj1_idle"), 0.1f, true);
            moveAnimation = new Animacion(mContentManager.Load<Texture2D>("Personajes/pj1_move"), 0.1f, true);
            fastpunchAnimation = new Animacion(mContentManager.Load<Texture2D>("Personajes/pj1_fastpunch"), 0.1f, true);
            midpunchAnimation = new Animacion(mContentManager.Load<Texture2D>("Personajes/pj1_fastpunch"), 0.1f, true);
            strongpunchAnimation = new Animacion(mContentManager.Load<Texture2D>("Personajes/pj1_fastpunch"), 0.1f, true);
            blockAnimation = new Animacion(mContentManager.Load<Texture2D>("Personajes/pj1_block"), 0.1f, true);
            jumpAnimation = new Animacion(mContentManager.Load<Texture2D>("Personajes/pj1_jump"), 0.1f, true);
        }


        /// <summary>
        /// Actualiza el estado del personaje en tiempo real.
        /// </summary>
        /// <param name="gameTime">Tiempo de juego.</param>
        /// <param name="Separacion">Separacion actual entre personajes.</param>
        /// <param name="IndiceSeparacion">Indice de posicion de personajes.</param>
        public void Update(GameTime gameTime, int Separacion, int IndiceSeparacion, bool Colision)
        {
            Ataque.tiempoPuñetazo -= gameTime.ElapsedGameTime.Milliseconds;

            KeyboardState aCurrentKeyboardState = Keyboard.GetState();

            Colisionado = Colision;

            // Llamamos a las diferentes funciones de actualización
            // de estado
            UpdateMovement(aCurrentKeyboardState, Separacion, IndiceSeparacion);
            UpdateJump(aCurrentKeyboardState);
            UpdatePunch(aCurrentKeyboardState);

            if (mCurrentState == State.Idle) Secuencia.PlayAnimation(idleAnimation);
            else if (mCurrentState == State.Walking) Secuencia.PlayAnimation(moveAnimation);
            else if (mCurrentState == State.Jumping) Secuencia.PlayAnimation(jumpAnimation);
            else if (mCurrentState == State.Blocking) Secuencia.PlayAnimation(blockAnimation);
            else if (mCurrentState == State.Attacking)
            {
                if (fast) Secuencia.PlayAnimation(fastpunchAnimation);
                else if (mid) Secuencia.PlayAnimation(midpunchAnimation);
                else if (strong) Secuencia.PlayAnimation(strongpunchAnimation);
            }

            // Fuerza que la vida quede entre 0 y 100 en todo momento
            // y los personajes no puedan salirse de los limites de la pantalla

            mPreviousKeyboardState = aCurrentKeyboardState;

            Posicion += mDirection * mSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;

            if (Ataque.tiempoPuñetazo <= 0)
                Ataque.tiempoPuñetazo = 0;
        }


        /// <summary>
        /// Actualiza el estado de movimiento del personaje, controla los valores de entrada.
        /// </summary>
        /// <param name="aCurrentKeyboardState">Entrada actual de teclado.</param>
        private void UpdateMovement(KeyboardState aCurrentKeyboardState, int Separacion, int IndiceSeparacion)
        {
            if (mCurrentState == State.Walking)
            {
                mSpeed = Vector2.Zero;
                mDirection = Vector2.Zero;
            }

            // Controles para personaje 1
            if (IndicePersonaje == 1)
            {
                if (Separacion < MaxSep)
                {
                    // Controles para cuando el personaje 1 está a la izquierda del 2.
                    if (IndiceSeparacion == 1)
                    {
                        if (Colisionado)
                        {

                            if (mCurrentState == State.Jumping)
                            {
                                MoverIzquierdaJ1 = true;
                                MoverDerechaJ1 = true;
                            }
                            else
                            {
                                MoverIzquierdaJ1 = true;
                                MoverDerechaJ1 = false;
                            }
                        }
                        else
                        {
                            MoverIzquierdaJ1 = true;
                            MoverDerechaJ1 = true;
                        }
                    }
                    else
                    {
                        if (Colisionado)
                        {
                            if (mCurrentState == State.Jumping)
                            {
                                MoverIzquierdaJ1 = true;
                                MoverDerechaJ1 = true;
                            }
                            else
                            {
                                MoverIzquierdaJ1 = false;
                                MoverDerechaJ1 = true;
                            }
                        }
                        else
                        {
                            MoverIzquierdaJ1 = true;
                            MoverDerechaJ1 = true;
                        }
                    }
                }
                else if (Separacion >= MaxSep)
                {
                    // Controles para cuando el personaje 1 está a la izquierda del 2.
                    if (IndiceSeparacion == 1)
                    {
                        MoverIzquierdaJ1 = false;
                        MoverDerechaJ1 = true;
                    }
                    else
                    {
                        MoverIzquierdaJ1 = true;
                        MoverDerechaJ1 = false;
                    }
                }

                if (mCurrentState == State.Attacking)
                {
                    MoverIzquierdaJ1 = false;
                    MoverDerechaJ1 = false;
                    Bloqueando = false;
                }
            }

            // Controles para personaje 2.
            if (IndicePersonaje == 2)
            {
                if (Separacion < MaxSep)
                {
                    // Controles para cuando el personaje 1 está a la izquierda del 2.
                    if (IndiceSeparacion == 1)
                    {
                        if (Colisionado)
                        {
                            if (mCurrentState == State.Jumping)
                            {
                                MoverIzquierdaJ2 = true;
                                MoverDerechaJ2 = true;
                            }
                            else
                            {
                                MoverIzquierdaJ2 = false;
                                MoverDerechaJ2 = true;
                            }
                        }
                        else
                        {
                            MoverIzquierdaJ2 = true;
                            MoverDerechaJ2 = true;
                        }
                    }
                    // Controles para cuando el personaje 1 está a la derecha del 2.
                    else
                    {
                        if (Colisionado)
                        {
                            if (mCurrentState == State.Jumping)
                            {
                                MoverIzquierdaJ2 = true;
                                MoverDerechaJ2 = true;
                            }
                            else
                            {
                                MoverIzquierdaJ2 = true;
                                MoverDerechaJ2 = false;
                            }
                        }
                        else
                        {
                            MoverIzquierdaJ2 = true;
                            MoverDerechaJ2 = true;
                        }
                    }
                }
                else if (Separacion >= MaxSep)
                {
                    // Controles para cuando el personaje 1 está a la izquierda del 2.
                    if (IndiceSeparacion == 1)
                    {
                        MoverIzquierdaJ2 = true;
                        MoverDerechaJ2 = false;
                    }
                    // Controles para cuando el personaje 1 está a la derecha del 2.
                    else
                    {
                        MoverIzquierdaJ2 = false;
                        MoverDerechaJ2 = true;
                    }
                }

                // Si estamos atacando, no podemos movernos.
                if (mCurrentState == State.Attacking)
                {
                    MoverIzquierdaJ2 = false;
                    MoverDerechaJ2 = false;
                    Bloqueando = false;
                }
            }

            if (IndicePersonaje == 1)
            {
                if (aCurrentKeyboardState.IsKeyDown(Keys.A) && Posicion.X > MinLim)
                {
                    if (MoverIzquierdaJ1)
                    {
                        if (mCurrentState != State.Jumping)
                        {
                            mCurrentState = State.Walking;
                        }
                        mSpeed.X = CHAR1_SPEED;
                        mDirection.X = MOVE_LEFT;
                    }
                    // Siempre que el Jugador se mueva, dejará de bloquear.
                    Bloqueando = false;
                }
                else if (aCurrentKeyboardState.IsKeyDown(Keys.D) && Posicion.X < MaxLim - contenedorPersonaje.Width)
                {
                    if (MoverDerechaJ1)
                    {
                        if (mCurrentState != State.Jumping)
                        {
                            mCurrentState = State.Walking;
                        }
                        mSpeed.X = CHAR1_SPEED;
                        mDirection.X = MOVE_RIGHT;
                    }
                    // Siempre que el Jugador se mueva, dejará de bloquear.
                    Bloqueando = false;
                }
                // Unicamente se dará el bloqueo cuando no se ataque y estemos a cierta distancia.
                else if (Separacion < 300 && mCurrentState != State.Attacking && mCurrentState != State.Jumping)
                {
                    Bloqueando = true;
                    if (Bloqueando) mCurrentState = State.Blocking;
                }
                // Sino está moviendose ni bloqueando, solo se encuentra en pie.
                else if (mCurrentState != State.Jumping && mCurrentState != State.Attacking && mCurrentState != State.Blocking) mCurrentState = State.Idle;
                
            }

            if (IndicePersonaje == 2)
            {
                if (aCurrentKeyboardState.IsKeyDown(Keys.Left) && Posicion.X > MinLim)
                {
                    if (MoverIzquierdaJ2)
                    {
                        if (mCurrentState != State.Jumping)
                        {
                            mCurrentState = State.Walking;
                        }
                        mSpeed.X = CHAR1_SPEED;
                        mDirection.X = MOVE_LEFT;
                    }
                    // Siempre que el Jugador se mueva, dejará de bloquear.
                    Bloqueando = false;
                }
                else if (aCurrentKeyboardState.IsKeyDown(Keys.Right) && Posicion.X < MaxLim - contenedorPersonaje.Width)
                {
                    if (MoverDerechaJ2)
                    {
                        if (mCurrentState != State.Jumping)
                        {
                            mCurrentState = State.Walking;
                        }
                        mSpeed.X = CHAR1_SPEED;
                        mDirection.X = MOVE_RIGHT;
                    }
                    // Siempre que el Jugador se mueva, dejará de bloquear.
                    Bloqueando = false;
                }
                // Unicamente se dará el bloqueo cuando no se ataque y estemos a cierta distancia.
                else if (Separacion < 300 && mCurrentState != State.Attacking && mCurrentState != State.Jumping)
                {
                    Bloqueando = true;
                    if (Bloqueando) mCurrentState = State.Blocking;
                }
                // Sino está moviendose ni bloqueando, solo se encuentra en pie.
                else if (mCurrentState != State.Jumping && mCurrentState != State.Attacking && mCurrentState != State.Blocking) mCurrentState = State.Idle;
                
            }
            
        }

        /// <summary>
        /// Actualiza el estado de salto de un personaje.
        /// </summary>
        /// <param name="aCurrentKeyboardState">Entrada actual de teclado.</param>
        private void UpdateJump(KeyboardState aCurrentKeyboardState)
        {
            if (mCurrentState == State.Walking || mCurrentState == State.Idle)
            {
                if (IndicePersonaje == 1)
                {
                    if (aCurrentKeyboardState.IsKeyDown(Keys.Space) == true && mPreviousKeyboardState.IsKeyDown(Keys.Space) == false)
                    {
                        mDirection.Y = MOVE_UP;
                        mSpeed.Y = 400;
                        mCurrentState = State.Jumping;
                    }
                }
                else
                {
                    if (aCurrentKeyboardState.IsKeyDown(Keys.Enter) == true && mPreviousKeyboardState.IsKeyDown(Keys.Enter) == false)
                    {
                        mDirection.Y = MOVE_UP;
                        mSpeed.Y = 400;
                        mCurrentState = State.Jumping;
                    }
                }
            }

            // Si el personaje se encuentra "Saltando" aceleramos dependiendo de la dirección
            // y deceleramos en cuanto ésta cambia.
            if (mCurrentState == State.Jumping)
            {
                if (mSpeed.Y * mDirection.Y < 400)
                {
                    mSpeed.Y += mDirection.Y * 10f;
                    if (mSpeed.Y == 0)
                    {
                        mDirection.Y = MOVE_DOWN;
                    }
                }
            }

            // Comprobamos la posicion actual del personaje, si es menor a la inicial,
            // reseteamos a su posición inicial y declaramos la dirección y velocidad nula.
            if (Posicion.Y > START_POSITION_Y)
            {
                Posicion.Y = START_POSITION_Y;
                mSpeed = Vector2.Zero;
                mDirection = Vector2.Zero;
                mCurrentState = State.Idle;
            }
        }

        /// <summary>
        /// Actualiza el ataque del personaje.
        /// </summary>
        /// <param name="aCurrentKeyboardState">Entrada actual de teclado.</param>
        public void UpdatePunch(KeyboardState aCurrentKeyboardState)
        {
            if (IndicePersonaje == 1 && mCurrentState!= State.Jumping)
            {
                if (aCurrentKeyboardState.IsKeyDown(Keys.H) == true)
                {
                    if (!fast)
                    {
                        fast = true;
                        Punch(1);
                    }
                }
                else if (aCurrentKeyboardState.IsKeyDown(Keys.J) == true)
                {
                    if (!mid)
                    {
                        mid = true;
                        Punch(2);
                    }
                }
                else if (aCurrentKeyboardState.IsKeyDown(Keys.K) == true)
                {
                    if (!strong)
                    {
                        strong = true;
                        Punch(3);
                    }
                }
                else if (Ataque.tiempoPuñetazo <= 0 && mCurrentState == State.Attacking)
                {
                    fast = false;
                    mid = false;
                    strong = false;
                    Atacando = false;
                    mCurrentState = State.Idle;
                }
            }
            else
            {
                if (aCurrentKeyboardState.IsKeyDown(Keys.NumPad4) == true)
                {
                    if (!fast)
                    {
                        fast = true;
                        Punch(1);
                    }
                }
                else if (aCurrentKeyboardState.IsKeyDown(Keys.NumPad5) == true)
                {
                    if (!mid)
                    {
                        mid = true;
                        Punch(2);
                    }
                }
                else if (aCurrentKeyboardState.IsKeyDown(Keys.NumPad6) == true)
                {
                    if (!strong)
                    {
                        strong = true;
                        Punch(3);
                    }
                }

                // Considera que estamos atacando hasta que termina el ataque.
                else if (Ataque.tiempoPuñetazo <= 0 && mCurrentState == State.Attacking && !Ataque.golpeAcertado)
                {
                    fast = false;
                    mid = false;
                    strong = false;
                    mCurrentState = State.Idle;
                }
            }
        }
        /// <summary>
        /// Controla los distintos tipos de puñetazo.
        /// </summary>
        private void Punch(int tipoPuñetazo)
        {
            // Resetea el marcador de estado de ataque.
            mCurrentState = State.Attacking;

            if (Ataque.tiempoPuñetazo <= 0 && !Ataque.golpeAcertado)
            {
                if (Colisionado)
                {
                    if (tipoPuñetazo == 1) Ataque.golpe = Ataque.golpeBajo;
                    else if (tipoPuñetazo == 2) Ataque.golpe = Ataque.golpeMedio;
                    else if (tipoPuñetazo == 3) Ataque.golpe = Ataque.golpeAlto;
                }
                else
                {
                    Ataque.golpe = 0;
                }

                if (tipoPuñetazo == 1) Ataque.tiempoPuñetazo = 300;
                else if (tipoPuñetazo == 2) Ataque.tiempoPuñetazo = 500;
                else if (tipoPuñetazo == 3) Ataque.tiempoPuñetazo = 1000;

                Ataque.golpeAcertado = true;
            }
        }

        public void Draw(GameTime gameTime, SpriteBatch mSpriteBatch)
        {
            // Flip the sprite to face the way we are moving.
            if (IndicePersonaje == 1 && Firstload == true)
            {
                flip = SpriteEffects.FlipHorizontally;
                Firstload = false;
            }
            if (mDirection.X > 0)
                flip = SpriteEffects.FlipHorizontally;
            else if (mDirection.X < 0)
                flip = SpriteEffects.None;

            //mSpriteBatch.Draw(Textura, Posicion, Size, Color.White, 0.0f, Vector2.Zero, Escala, SpriteEffects.None, PROFUNDIDAD);
            if (IndicePersonaje == 1) ColorJugador = Color.LightGreen;
            else ColorJugador = Color.LawnGreen;
            Secuencia.Draw(gameTime, mSpriteBatch, Posicion, flip, ColorJugador);
        }
    }
}
